#region File Description
//-----------------------------------------------------------------------------
// SimpleAnimation.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using BehaviorTrees;
using Microsoft.Xna.Framework.Audio;
#endregion

namespace SimpleAnimation
{
    /// <summary>
    /// Sample showing how to apply simple animation to a rigid body tank model.
    /// </summary>
    public class SimpleAnimationGame : Microsoft.Xna.Framework.Game
    {
        #region Fields

        GraphicsDeviceManager graphics;
		Matrix view;
		Matrix projection;
		// for tank creation
		Vector3 from = new Vector3();
		Vector3 to = new Vector3();

        Tank tank;
		Tank tank2;
        #endregion

        #region Initialization


        public SimpleAnimationGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 853;
            graphics.PreferredBackBufferHeight = 480;

			// your tank
			tank = new Tank();
			tank.Position = new Vector3(4000f, 0f, -2500f);
			tank.Velocity = 2000f;
			//SetTank(tank, new Vector3(-1500f, 0f, 2500f), new Vector3(500f, 0f, 500f));
			
			// this is the patroling tank
			tank2 = new Tank();
			// set the hostility
			tank.AddEnemy(tank2);
			tank2.AddEnemy(tank);
			// handicap
			tank2.MaxHitPoints = 400;
			tank2.Position = new Vector3(2000f, 0f, -1000f);
			//tank2.Direction = Vector3.Left; <- does it change the angle as well?
			tank2.Behavior = new Repeater<Tank>(new Sequence<Tank>()
			{
				// patroling
				// using FindEnemy as condition is somewhat bad idea, cause it has side-effects(setting the ref to enemy)
				new Watch<Tank>(Tank.FindEnemy,
				    // watch the condition and patrol during watching
				    new Repeater<Tank>(0, new Sequence<Tank>()
				    {
				        new MoveToBehavior<Tank>(new Vector3(-2000f,0f,-1000f)),
						new MoveToBehavior<Tank>(new Vector3(-2000f,0f,1000f)),
				        new MoveToBehavior<Tank>(new Vector3(2000f,0f,1000f)),
						new MoveToBehavior<Tank>(new Vector3(2000f,0f,-1000f))
				    })
				),
				// attacking
				new Watch<Tank>(Tank.EnemyDead,
				    new Repeater<Tank>(new Wait<Tank>(1, 
				        new Sequence<Tank>()
				        {
				            new DestroyEnemyBehavior<Tank>(),
				            new BehaviorTrees.Action<Tank>(Tank.Damage)
				        })
				    )
				),
				new ResetAimBehavior<Tank>(),
			});

            // scheduler
           // scheduler = new Scheduler(this);
           // scheduler.Add(tankBehavior);
           // Components.Add(scheduler);
        }

		AudioEngine audioEngine;
		WaveBank waveBank;
		SoundBank soundBank;
		Cue engineSound = null;

		protected override void Initialize()
		{
			audioEngine = new AudioEngine("Content\\explosion.xgs");
			waveBank = new WaveBank(audioEngine, "Content\\Wave Bank.xwb");
			soundBank = new SoundBank(audioEngine, "Content\\Sound Bank.xsb");
			engineSound = soundBank.GetCue("largex");
			this.IsMouseVisible = true;
			base.Initialize();
		}

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            tank.Load(Content);
			tank2.Load(Content);
        }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Allows the game to run logic.
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            HandleInput();

            float time = (float)gameTime.TotalGameTime.TotalSeconds;

			tank.Update(gameTime);
			tank2.Update(gameTime);

            base.Update(gameTime);
        }


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice device = graphics.GraphicsDevice;

            device.Clear(Color.DarkGray);

            // Calculate the camera matrices.
            Viewport viewport = device.Viewport;

            float aspectRatio = (float)viewport.Width / (float)viewport.Height;

            float time = (float)gameTime.TotalGameTime.TotalSeconds;

			view = Matrix.CreateLookAt(new Vector3(0, 5000, 3000),
								  new Vector3(0, 0, 0),
								  Vector3.Up);

			projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
																	aspectRatio,
																	10,
																	10000);

			DrawTank(tank, view, projection);
			DrawTank(tank2, view, projection);

            base.Draw(gameTime);
        }
		// helper
		void DrawTank(Tank tank, Matrix view, Matrix projection)
		{
			//Matrix rotation = Matrix.CreateRotationY(time * 0.1f);
			// backward is default tank orientation
			//float angle = (float)Math.Acos(Vector3.Dot(Vector3.Backward, tank.Direction));
			Matrix world = Matrix.CreateRotationY(tank.Angle) * Matrix.CreateTranslation(tank.Position);

			// Draw the tank model.
			tank.Draw(world, view, projection);

			// and now something entirely different
			if (tank.bam)
			{
				soundBank.PlayCue("largex");
				tank.bam = false;

			}
		}
        #endregion

        #region Handle Input


        /// <summary>
        /// Handles input for quitting the game.
        /// </summary>
        private void HandleInput()
        {
            KeyboardState currentKeyboardState = Keyboard.GetState();
            GamePadState currentGamePadState = GamePad.GetState(PlayerIndex.One);

            // Check for exit.
            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
                currentGamePadState.Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }
			if(currentKeyboardState.IsKeyDown(Keys.Enter))
			{
				tank2.HitPoints = tank2.MaxHitPoints;
			}
			if (currentKeyboardState.IsKeyDown(Keys.Space))
			{
				tank.HitPoints += 100;
			}
			if (Mouse.GetState().LeftButton == ButtonState.Pressed)
			{
				SetTankMoveTarget(tank, GetMPos());
			}
			if (Mouse.GetState().RightButton == ButtonState.Pressed)
			{
				if(!tank.IsAttacking)
					SetTankTarget(tank);
			}
        }
		private void SetTankMoveTarget(Tank tank, Vector3 target)
		{
			tank.MoveTarget = target;
			// so we see in this case passing target to constructor and using it later
			// is redundant...
			tank.Behavior = new MoveToBehavior<Tank>(target);
		}
		// checks if ray intersects enemy volume
		// and set destroy behavior
		private void SetTankTarget(Tank tank)
		{
			Vector3 near = new Vector3((float)Mouse.GetState().X, (float)Mouse.GetState().Y, 0f);
			near = GraphicsDevice.Viewport.Unproject(near, projection, view, Matrix.Identity);
			Vector3 far = new Vector3((float)Mouse.GetState().X, (float)Mouse.GetState().Y, 1f);
			far = GraphicsDevice.Viewport.Unproject(far, projection, view, Matrix.Identity);

			Ray ray = new Ray(near, Vector3.Normalize(far - near));
			if (ray.Intersects(tank2.BSphere) != null)
			{
				tank.Enemy = tank2;
				tank.Behavior = new Wait<Tank>(0.5, new DestroyEnemyBehavior<Tank>());
			}
		}
		
		private Vector3 GetMPos()
		{
			Vector3 near = new Vector3((float)Mouse.GetState().X, (float)Mouse.GetState().Y, 0f);
			near = GraphicsDevice.Viewport.Unproject(near, projection, view, Matrix.Identity);
			Vector3 far = new Vector3((float)Mouse.GetState().X, (float)Mouse.GetState().Y, 1f);
			far = GraphicsDevice.Viewport.Unproject(far, projection, view, Matrix.Identity);

			Ray ray = new Ray(near, Vector3.Normalize(far - near));
			Plane pl = new Plane(Vector3.Up, 0f);

			float? d = ray.Intersects(pl);

			near += ray.Direction * d.Value;
			return near;
		}


        #endregion
    }


    #region Entry Point

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static class Program
    {
        static void Main()
        {
            using (SimpleAnimationGame game = new SimpleAnimationGame())
            {
                game.Run();
            }
        }
    }

    #endregion
}
